package com.gitee.wsl.transform.encode.pinyin.trie

import com.gitee.wsl.transform.encode.pinyin.trie.handler.EmitHandler
import com.gitee.wsl.transform.encode.pinyin.trie.handler.PayloadEmitDelegateHandler
import com.gitee.wsl.transform.encode.pinyin.trie.handler.StatefulEmitHandler
import com.gitee.wsl.transform.encode.pinyin.trie.handler.StatefulPayloadEmitDelegateHandler

/**
 * Based on the [Aho-Corasick white
 * paper](http://cr.yp.to/bib/1975/aho.pdf), from Bell technologies.
 *
 * @author Robert Bor
 */
class Trie private constructor(private val payloadTrie: PayloadTrie<String>) {

    fun tokenize(text: String): Collection<Token> {
        val tokens = this.payloadTrie.tokenize(text)
        return asTokens(tokens)
    }

    fun parseText(text: CharSequence): Collection<Emit> {
        val parsedText = this.payloadTrie.parseText(text)
        return asEmits(parsedText)
    }


    fun parseText(text: CharSequence, emitHandler: StatefulEmitHandler): Collection<Emit> {
        val parsedText = this.payloadTrie.parseText(
            text,
            StatefulPayloadEmitDelegateHandler(emitHandler)
        )
        return asEmits(parsedText)
    }

    fun containsMatch(text: CharSequence): Boolean {
        return firstMatch(text) != null
    }

    fun parseText(text: CharSequence, emitHandler: EmitHandler) {
        this.payloadTrie.parseText(text, PayloadEmitDelegateHandler(emitHandler))
    }

    /**
     * The first matching text sequence.
     *
     * @param text The text to search for keywords, must not be `null`.
     * @return `null` if no matches found.
     */
    fun firstMatch(text: CharSequence): Emit? {

        val payload= this.payloadTrie.firstMatch(text)
        return if (payload == null)
            null
        else
            Emit(
                payload.start,
                payload.end,
                payload.keyword
            )
    }

    class TrieBuilder() {
        private val delegate: PayloadTrie.PayloadTrieBuilder<String> = PayloadTrie.builder()

        /**
         * Configure the Trie to ignore case when searching for keywords in the text.
         * This must be called before calling addKeyword because the algorithm converts
         * keywords to lowercase as they are added, depending on this case sensitivity
         * setting.
         *
         * @return This builder.
         */
        fun ignoreCase(): TrieBuilder {
            delegate.ignoreCase()
            //            this.trieConfig.setCaseInsensitive(true);
            return this
        }

        /**
         * Configure the Trie to ignore overlapping keywords.
         *
         * @return This builder.
         */
        fun ignoreOverlaps(): TrieBuilder {
            delegate.ignoreOverlaps()
            return this
        }

        /**
         * Adds a keyword to the Trie's list of text search keywords.
         *
         * @param keyword The keyword to add to the list.
         * @return This builder.
         * @throws NullPointerException if the keyword is null.
         */
        fun addKeyword(keyword: String): TrieBuilder {
            delegate.addKeyword(keyword, null)
            return this
        }

        /**
         * Adds a list of keywords to the Trie's list of text search keywords.
         *
         * @param keywords The keywords to add to the list.
         * @return This builder.
         */
        fun addKeywords(vararg keywords: String): TrieBuilder {
            for (keyword in keywords) {
                delegate.addKeyword(keyword, null)
            }
            return this
        }

        /**
         * Adds a list of keywords to the Trie's list of text search keywords.
         *
         * @param keywords The keywords to add to the list.
         * @return This builder.
         */
        fun addKeywords(keywords: Collection<String>): TrieBuilder {
            for (keyword in keywords) {
                this.delegate.addKeyword(keyword, null)
            }
            return this
        }

        /**
         * Configure the Trie to match whole keywords in the text.
         *
         * @return This builder.
         */
        fun onlyWholeWords(): TrieBuilder {
            this.delegate.onlyWholeWords()
            return this
        }

        /**
         * Configure the Trie to match whole keywords that are separated by whitespace
         * in the text. For example, "this keyword thatkeyword" would only match the
         * first occurrence of "keyword".
         *
         * @return This builder.
         */
        fun onlyWholeWordsWhiteSpaceSeparated(): TrieBuilder {
            this.delegate.onlyWholeWordsWhiteSpaceSeparated()
            return this
        }

        /**
         * Configure the Trie to stop after the first keyword is found in the text.
         *
         * @return This builder.
         */
        fun stopOnHit(): TrieBuilder {
            this.delegate.stopOnHit()
            return this
        }

        /**
         * Configure the Trie based on the builder settings.
         *
         * @return The configured Trie.
         */
        fun build(): Trie {
            val payloadTrie = this.delegate.build()
            return Trie(payloadTrie)
        }

        /**
         * @return This builder.
         */
        @Deprecated("Use ignoreCase()")
        fun caseInsensitive(): TrieBuilder {
            return ignoreCase()
        }

        /**
         * @return This builder.
         */
        @Deprecated("Use ignoreOverlaps()")
        fun removeOverlaps(): TrieBuilder {
            return ignoreOverlaps()
        }
    }

    companion object {
        private fun asTokens(tokens: Collection<PayloadToken<String>>): Collection<Token> {
            val result = ArrayList<Token>()
            for (payloadToken in tokens) {
                result.add(DefaultToken(payloadToken))
            }
            return result
        }

        private fun asEmits(emits: Collection<PayloadEmit<String>>): Collection<Emit> {
            val result = ArrayList<Emit>()
            for (emit in emits) {
                result.add(asEmit(emit))
            }
            return result
        }

        private fun asEmit(payloadEmit: PayloadEmit<String>): Emit {
            return Emit(payloadEmit.start, payloadEmit.end, payloadEmit.keyword)
        }

        /**
         * Provides a fluent interface for constructing Trie instances.
         *
         * @return The builder used to configure its Trie.
         */
        fun builder(): TrieBuilder {
            return TrieBuilder()
        }
    }
}
